/*
 * Package il.ac.biu.cs.grossmm.api.keys
 * File PatternMatcher.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.api.keys;

import il.ac.biu.cs.grossmm.api.keys.Attribute;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.api.keys.PatternEntry;

public class PatternMatcher {
	final KeyPattern pattern;
	
	public static final int INTERSECTION = 1; // binary 001

	public static final int LEFT_RESTRICTION = 3; // binary 011

	public static final int RIGHT_RESTRICTION = 5; // binary 101

	public static final int EQUALITY = LEFT_RESTRICTION | RIGHT_RESTRICTION;

	public static final int DISJOINT = 0;

	public PatternMatcher(KeyPattern pattern)
	{
		this.pattern = pattern;
	}

	public boolean matches(Key key)
	{
		return matches(pattern, key);
	}

	public static boolean matches(KeyPattern pattern, Key key)
	{
		return valueMatches(pattern, key) && subPatternsMatch(pattern, key);
	}

	@SuppressWarnings("unchecked")
	static boolean valueMatches(KeyPattern pattern, Key key)
	{
		Object keyValue = key.value();
		// Parameter parameter = pattern.getParameter();
		Object patternValue = pattern.value();

		if (patternValue != null) {
			if (keyValue == null)
				return patternValue == null;
			else
				return keyValue.equals(patternValue);
		} else {
			if (keyValue == null)
				return true;
			else
				return pattern.valueClass().isAssignableFrom(
					keyValue.getClass());
		}
	}

	static boolean subPatternsMatch(KeyPattern pattern, Key key)
	{
		int size = pattern.size();

		if (!pattern.isMask() && size != key.size())
			return false;

		for (int i = 0; i < size; i++) {
			PatternEntry patternEntry = pattern.entry(i);

			Key subkey = key.subkey(patternEntry.getAttribute());
			if (subkey == null) {
				if (patternEntry.isMandatory())
					return false;
				else
					continue;
			}

			if (!matches(patternEntry.getPattern(), subkey))
				return false;
		}

		return true;
	}

	

	public static int compare(KeyPattern pattern1, KeyPattern pattern2)
	{
		// compare values and/or classes
		int c = compareValuesOf(pattern1, pattern2);

		boolean isMask1 = pattern1.isMask();
		boolean isMask2 = pattern2.isMask();
		
		if( isMask1 ) {
			if( !isMask2 )
				c &= RIGHT_RESTRICTION;
		} else {
			if( isMask2 )
				c &= LEFT_RESTRICTION;
		}

		int size1 = pattern1.size();

		// first pass (iterate attributes of pattern1)
		for (int i = 0; c != DISJOINT && i < size1; i++) {
			PatternEntry pe1 = pattern1.entry(i);
			Attribute a1 = pe1.getAttribute();

			PatternEntry pe2 = pattern2.entry(a1);

			int csub;

			if (pe2 == null) {
				//attribute a1 belongs to the left pattern but
				// does not belong to the righ pattern
				
				if( isMask2 )
					csub = LEFT_RESTRICTION;
				else if( pe1.isMandatory() )
					csub = DISJOINT;
				else
					csub = RIGHT_RESTRICTION;
			} else {
				KeyPattern sp1 = pe1.getPattern();
				KeyPattern sp2 = pe2.getPattern();
				
				csub = compare(sp1, sp2);
				
				if( !pe1.isMandatory() ) {
					if( !pe2.isMandatory() )
						csub |= INTERSECTION;
					else
						csub &= RIGHT_RESTRICTION;
				} else {
					if( !pe2.isMandatory() )
						csub &= LEFT_RESTRICTION;
				}
			}

			c &= csub;
		}
		
		int size2 = pattern2.size();
		
		// second pass (iterate attributes of pattern2)
		for (int i = 0; c != DISJOINT && i < size2; i++) {
			PatternEntry pe2 = pattern2.entry(i);
			Attribute a2 = pe2.getAttribute();
			
			// check only such entries in pattern2 where entry attribute
			// does not appear in pattern1 
			if( pattern1.entry(a2) != null )
				continue;
			
			int csub;
			
			if( isMask1 )
				csub = RIGHT_RESTRICTION;
			else if( pe2.isMandatory() )
				csub = DISJOINT;
			else
				csub = LEFT_RESTRICTION;
			
			 c &=csub;
		}
		
		return c;
	}

	@SuppressWarnings("unchecked")
	private static int compareValuesOf(KeyPattern pattern1, KeyPattern pattern2)
	{
		int c;

		Class class1 = pattern1.valueClass();
		Class class2 = pattern2.valueClass();

		if (class1 == null) {
			// left pattern specifies a value, not a class

			Object value1 = pattern1.value();
			if (class2 == null) {
				// right pattern specifies a value also
				Object value2 = pattern2.value();
				if (value1 == null ? value2 == null : value1.equals(value2))
					c = EQUALITY;
				else
					c = DISJOINT;
			} else {
				// right pattern specifies a class
				if (value1 == null
					|| class2.isAssignableFrom(value1.getClass()))
					c = LEFT_RESTRICTION;
				else
					c = DISJOINT;
			}
		} else {
			// left pattern specifies a class

			if (class2 == null) {
				// right pattern specifies a value
				Object value2 = pattern2.value();
				if (value2 == null
					|| class1.isAssignableFrom(value2.getClass()))
					c = RIGHT_RESTRICTION;
				else
					c = DISJOINT;
			} else {
				// right pattern specifies a class also
				if (class1.equals(class2))
					c = EQUALITY;
				else if( class1.isAssignableFrom(class2) )
					c = RIGHT_RESTRICTION;
				else if( class2.isAssignableFrom(class1) )
					c = LEFT_RESTRICTION;
				else
					c = INTERSECTION; // null is a common object
			}
		}

		return c;
	}
}
